﻿@page "/ru/latest/configuration/scripts"
@{
    Layout = "_ArticleLayout";
    ViewBag.Title = "Скрипты и формулы";
}

<nav class="doc-toc">
    <div class="h6">На этой странице</div>
    <hr>
    <ul>
        <li><a href="#script-rules">Правила создания скриптов</a></li>
        <li><a href="#available-variables">Доступные переменные</a></li>
        <li><a href="#available-functions">Доступные функции</a></li>
        <li><a href="#template-functions">Функции из шаблона проекта</a></li>
        <li><a href="#debugging">Отладка скриптов</a></li>
        <li><a href="#examples">Примеры формул</a></li>
    </ul>
</nav>

<div class="doc-content">
    <h1>Скрипты и формулы</h1>
    <p>В приложение Сервер встроен механизм исполнения пользовательских скриптов. Скрипты применяются для расчёта значений и статусов каналов, а также для расчёта значений команд управления.</p>
    <p>Скрипты записываются в таблицу <strong>Скрипты</strong> базы конфигурации. Созданный по шаблону проект уже содержит начальный набор скриптов, который удобно использовать в качестве примеров для разработки собственных скриптов. Переменные и функции, реализованные в таблице <strong>Скрипты</strong>, затем вызываются в столбцах <strong>Входная формула</strong> и <strong>Выходная формула</strong> таблицы <strong>Каналы</strong>. Чтобы расчёт по формуле для какого-либо канала выполнялся, необходимо установить для него галочку в столбце <strong>Формула вкл</strong>.</p>

    <h2 id="script-rules">Правила создания скриптов</h2>
    <p>Общие правила написания и использования скриптов:</p>
    <ol>
        <li>Скрипты записываются согласно <a href="https://learn.microsoft.com/ru-ru/dotnet/csharp/language-reference/" target="_blank">синтаксису языка C#</a>. Доступны различные классы фреймворка .NET, например, Math, DateTime, File.</li>
        <li>В таблицу <strong>Скрипты</strong> добавляются новые константы, поля, свойства и методы, которые становятся доступны в формулах каналов.</li>
        <li>Если хотя бы один скрипт или формула содержит ошибку, работа Сервера невозможна. Информация об ошибках в скриптах выводится в журнал приложения.</li>
    </ol>

    <p>Правила вычисления формул каналов:</p>
    <ol>
        <li>Расчёт входной формулы для каналов типа <em>Входной</em> и <em>Входной/выходной</em> выполняется только при получении Сервером новых данных по этим каналам. Используйте эти типы каналов, если данные приходят от устройств.</li>
        <li>Расчёт входной формулы для каналов типа <em>Расчётный</em> и <em>Расчётный/выходной</em> выполняется постоянно на каждом шаге основного цикла Сервера. Последовательность расчёта – от меньших номеров каналов к большим. Расчётные типы каналов используются, если значение и статус канала вычисляются на основе данных других каналов.</li>
        <li>Расчёт выходной формулы для каналов типа <em>Входной/выходной</em>, <em>Расчётный/выходной</em> и <em>Выходной</em> выполняется при отправке команды управления на соответствующий канал.</li>
        <li>Статус канала после вычисления входной формулы для каналов типа <em>Входной</em> и <em>Входной/выходной</em> равен статусу переданных Серверу данных, если расчёт статуса не задан в формуле явно.</li>
        <li>Для каналов типа <em>Расчётный</em> и <em>Расчётный/выходной</em> устанавливается статус <em>Определён</em>, если расчёт статуса не задан в формуле явно.</li>
        <li>Если входная формула канала содержит символ &quot;;&quot;, то она разбивается на две части: первая часть рассчитывает значение канала, вторая часть – статус канала.</li>
        <li>Если для канала заданы границы, то статус канала пересчитывается с учётом границ после вычисления входной формулы канала.</li>
        <li>Формулы должны возвращать значения определённых типов данных, которые описаны ниже.</li>
    </ol>

    <p>Входные формулы каналов возвращают данные следующих типов:</p>
    <table class="table table-hover">
        <thead>
            <tr>
                <th>Тип данных</th>
                <th>Описание</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>double</td>
                <td>Значение канала</td>
            </tr>
            <tr>
                <td>CnlData</td>
                <td>Значение и статус канала</td>
            </tr>
            <tr>
                <td>long</td>
                <td>64-битное целое значение канала, для которого в таблице <strong>Каналы</strong> установлен тип данных <em>Integer</em></td>
            </tr>
            <tr>
                <td>string</td>
                <td>Строковое значение канала, для которого в таблице <strong>Каналы</strong> установлен тип данных <em>ASCII string</em> или <em>Unicode string</em></td>
            </tr>
        </tbody>
    </table>
    <p>Если входная формула канала возвращает другой тип данных, не указанный таблице, возвращаемое формулой значение приводится к соответствующему типу в зависимости от типа данных канала. Часть входной формулы канала, которая рассчитывает статус канала, должна возвращать целое число типа <code>int</code>.</p>

    <p>Выходные формулы каналов возвращают данные следующих типов:</p>
    <table class="table table-hover">
        <thead>
            <tr>
                <th>Тип данных</th>
                <th>Описание</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>double</td>
                <td>Значение команды. Чтобы отменить команду, формула должна вернуть <code>double.NaN</code></td>
            </tr>
            <tr>
                <td>CnlData</td>
                <td>Значение команды. Чтобы отменить команду, формула должна вернуть <code>CnlData.Empty</code></td>
            </tr>
            <tr>
                <td>byte[]</td>
                <td>Бинарные данные команды. Чтобы отменить команду, формула должна вернуть <code>null</code></td>
            </tr>
            <tr>
                <td>string</td>
                <td>Строковые данные команды. Преобразуются Сервером в массив байт</td>
            </tr>
        </tbody>
    </table>

    <h2 id="available-variables">Доступные переменные</h2>
    <p>Механизм скриптов предоставляет следующие встроенные переменные:</p>
    <table class="table table-hover">
        <thead>
            <tr>
                <th>Переменная</th>
                <th>Тип данных</th>
                <th>Описание</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>Timestamp</td>
                <td>DateTime</td>
                <td>Метка времени обрабатываемых данных (UTC)</td>
            </tr>
            <tr>
                <td>IsCurrent</td>
                <td>bool</td>
                <td>Обрабатываемые данные являются текущими данными</td>
            </tr>
            <tr>
                <td>CnlNum</td>
                <td>int</td>
                <td>Номер канала, формула которого вычисляется</td>
            </tr>
            <tr>
                <td>Channel</td>
                <td>Cnl</td>
                <td>Свойства канала, формула которого вычисляется</td>
            </tr>
            <tr>
                <td>ArrIdx</td>
                <td>int</td>
                <td>Индекс обрабатываемого элемента массива</td>
            </tr>
            <tr>
                <td>Cnl, CnlVal</td>
                <td>double</td>
                <td>Переданное Серверу значение канала до расчёта</td>
            </tr>
            <tr>
                <td>CnlStat</td>
                <td>int</td>
                <td>Переданный Серверу статус канала до расчёта</td>
            </tr>
            <tr>
                <td>CnlData</td>
                <td>CnlData</td>
                <td>Переданные Серверу данные канала до расчёта</td>
            </tr>
            <tr>
                <td>Cmd, CmdVal</td>
                <td>double</td>
                <td>Переданное Серверу значение команды управления</td>
            </tr>
            <tr>
                <td>CmdData</td>
                <td>byte[]</td>
                <td>Переданные Серверу данные команды управления</td>
            </tr>
            <tr>
                <td>CmdDataStr</td>
                <td>string</td>
                <td>Данные команды управления в строковом виде</td>
            </tr>
        </tbody>
    </table>

    <h2 id="available-functions">Доступные функции</h2>
    <p>Механизм скриптов предоставляет следующие встроенные функции:</p>
    <table class="table table-hover">
        <thead>
            <tr>
                <th>Функция</th>
                <th>Тип данных</th>
                <th>Описание</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>N(n)</td>
                <td>int</td>
                <td>Возвращает номер канала n. Используется при клонировании каналов</td>
            </tr>
            <tr>
                <td>Val()</td>
                <td>double</td>
                <td>Текущее значение канала вычисляемой формулы</td>
            </tr>
            <tr>
                <td>Val(n)</td>
                <td>double</td>
                <td>Текущее значение канала n</td>
            </tr>
            <tr>
                <td>SetVal(n, val)</td>
                <td>double</td>
                <td>Устанавливает текущее значение канала n</td>
            </tr>
            <tr>
                <td>Stat()</td>
                <td>int</td>
                <td>Текущий статус канала вычисляемой формулы</td>
            </tr>
            <tr>
                <td>Stat(n)</td>
                <td>int</td>
                <td>Текущий статус канала n</td>
            </tr>
            <tr>
                <td>SetStat(n, stat)</td>
                <td>int</td>
                <td>Устанавливает текущий статус канала n</td>
            </tr>
            <tr>
                <td>Data()</td>
                <td>CnlData</td>
                <td>Текущие данные канала вычисляемой формулы</td>
            </tr>
            <tr>
                <td>Data(n)</td>
                <td>CnlData</td>
                <td>Текущие данные канала n</td>
            </tr>
            <tr>
                <td>SetData(n, val, stat)</td>
                <td>double</td>
                <td>Устанавливает текущее значение и статус канала n</td>
            </tr>
            <tr>
                <td>SetData(n, cnlData)</td>
                <td>double</td>
                <td>Устанавливает текущие данные канала n</td>
            </tr>
            <tr>
                <td>NewData(val, stat)</td>
                <td>CnlData</td>
                <td>Создает новый экземпляр данных канала</td>
            </tr>
            <tr>
                <td>PrevVal()</td>
                <td>double</td>
                <td>Предыдущее значение канала вычисляемой формулы</td>
            </tr>
            <tr>
                <td>PrevVal(n)</td>
                <td>double</td>
                <td>Предыдущее значение канала n</td>
            </tr>
            <tr>
                <td>PrevStat()</td>
                <td>int</td>
                <td>Предыдущий статус канала вычисляемой формулы</td>
            </tr>
            <tr>
                <td>PrevStat(n)</td>
                <td>int</td>
                <td>Предыдущий статус канала n</td>
            </tr>
            <tr>
                <td>PrevData()</td>
                <td>CnlData</td>
                <td>Предыдущие данные канала вычисляемой формулы</td>
            </tr>
            <tr>
                <td>PrevData(n)</td>
                <td>CnlData</td>
                <td>Предыдущие данные канала n</td>
            </tr>
            <tr>
                <td>Time()</td>
                <td>DateTime</td>
                <td>Метка времени канала вычисляемой формулы</td>
            </tr>
            <tr>
                <td>Time(n)</td>
                <td>DateTime</td>
                <td>Метка времени канала n</td>
            </tr>
            <tr>
                <td>PrevTime()</td>
                <td>DateTime</td>
                <td>Предыдущая метка времени канала вычисляемой формулы</td>
            </tr>
            <tr>
                <td>PrevTime(n)</td>
                <td>DateTime</td>
                <td>Предыдущая метка времени канала n</td>
            </tr>
            <tr>
                <td>Deriv(n)</td>
                <td>double</td>
                <td>Производная значения канала n по времени</td>
            </tr>
        </tbody>
    </table>

    <h2 id="template-functions">Функции из шаблона проекта</h2>
    <p>В проекте, который был создан на основе стандартного шаблона, таблица <strong>Скрипты</strong> содержит следующие функции:</p>
    <table class="table table-hover">
        <thead>
            <tr>
                <th>Функция</th>
                <th>Тип данных</th>
                <th>Описание</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>GetBit(val, n)</td>
                <td>double</td>
                <td>Бит n значения val</td>
            </tr>
            <tr>
                <td>GetBit(cnlData, val)</td>
                <td>CnlData</td>
                <td>Данные канала, состоящие из бита n значения и статуса канала</td>
            </tr>
            <tr>
                <td>SetBit(val, n, isOn)</td>
                <td>double</td>
                <td>Устанавливает бит n значения val</td>
            </tr>
            <tr>
                <td>SetBit(cnlData, n, isOn)</td>
                <td>CnlData</td>
                <td>Устанавливает бит n значения канала, сохраняя статус канала неизменным</td>
            </tr>
            <tr>
                <td>GetByte(val, n)</td>
                <td>double</td>
                <td>Байт n значения val</td>
            </tr>
            <tr>
                <td>DataRel(offset)</td>
                <td>CnlData</td>
                <td>Данные канала относительно текущего канала</td>
            </tr>
            <tr>
                <td>SetData()</td>
                <td>double</td>
                <td>Устанавливает текущие данные канала в соответствии со значением команды</td>
            </tr>
            <tr>
                <td>Now()</td>
                <td>double</td>
                <td>Текущая дата и время в виде числа с плавающей точкой. Часовой пояс сервера</td>
            </tr>
            <tr>
                <td>UtcNow()</td>
                <td>double</td>
                <td>Текущая дата и время в виде числа с плавающей точкой. Универсальный часовой пояс (UTC)</td>
            </tr>
            <tr>
                <td>UnixTime()</td>
                <td>long</td>
                <td>Текущее время Unix в секундах</td>
            </tr>
            <tr>
                <td>EncodeDate(<wbr />dateTime)</td>
                <td>double</td>
                <td>Преобразует указанные дату и время в значение канала типа double</td>
            </tr>
            <tr>
                <td>DecodeDate(val)</td>
                <td>DateTime</td>
                <td>Преобразует значение канала в дату и время</td>
            </tr>
            <tr>
                <td>EncodeAscii(s)</td>
                <td>double</td>
                <td>Преобразует строку в кодировке ASCII, длиной до 8 символов, в число с плавающей точкой</td>
            </tr>
            <tr>
                <td>EncodeUnicode(s)</td>
                <td>double</td>
                <td>Преобразует строку в кодировке Unicode, длиной до 4 символов, в число с плавающей точкой</td>
            </tr>
            <tr>
                <td>DecodeAscii(val)</td>
                <td>string</td>
                <td>Преобразует заданное значение в строку ASCII длиной до 8 символов</td>
            </tr>
            <tr>
                <td>DecodeUnicode(<wbr />val)</td>
                <td>string</td>
                <td>Преобразует заданное значение в строку Unicode длиной до 4 символов</td>
            </tr>
            <tr>
                <td>Substring(s, startIndex, length)</td>
                <td>string</td>
                <td>Извлекает подстроку из строки с проверкой границ</td>
            </tr>
            <tr>
                <td>SplitAscii(<wbr />getStringFunc)</td>
                <td>string</td>
                <td>Разделяет строку ASCII для хранения несколькими каналами</td>
            </tr>
            <tr>
                <td>SplitUnicode(<wbr />getStringFunc)</td>
                <td>string</td>
                <td>Разделяет строку Unicode для хранения несколькими каналами</td>
            </tr>
            <tr>
                <td>EverySec(<wbr />getDataFunc)</td>
                <td>CnlData</td>
                <td>Выполняет указанную функцию каждую секунду</td>
            </tr>
            <tr>
                <td>EveryMin(<wbr />getDataFunc)</td>
                <td>CnlData</td>
                <td>Выполняет указанную функцию каждую минуту</td>
            </tr>
            <tr>
                <td>EveryHour(<wbr />getDataFunc)</td>
                <td>CnlData</td>
                <td>Выполняет указанную функцию каждый час</td>
            </tr>
            <tr>
                <td>CountPulse(<wbr />cnlNum)</td>
                <td>double</td>
                <td>Считает импульс указанного канала</td>
            </tr>
            <tr>
                <td>HourStarted()</td>
                <td>bool</td>
                <td>Указывает, что начался новый час. Результат равен true один раз для каждого канала</td>
            </tr>
            <tr>
                <td>DayStarted()</td>
                <td>bool</td>
                <td>Указывает, что начался новый день. Результат равен true один раз для каждого канала</td>
            </tr>
            <tr>
                <td>MonthStarted()</td>
                <td>bool</td>
                <td>Указывает, что начался новый месяц. Результат равен true один раз для каждого канала</td>
            </tr>
        </tbody>
    </table>

    <p>Дополнительные скрипты, в том числе для вычисления средних значений, доступны на <a href="https://github.com/RapidScada/scada-community/tree/master/Formulas" target="_blank">GitHub</a>.</p>

    <h2 id="debugging">Отладка скриптов</h2>
    <p>При разработке собственных скриптов необходимо соблюдать правила синтаксиса и проверять правильность работы скриптов. Если службе Сервера при запуске не удалось скомпилировать скрипты, информация об ошибке выводится в журнал работы Сервера <code>ScadaServer.log</code>, а компилируемый исходный код доступен в файле <code>CalcEngine.cs</code>, который расположен в директории журналов Сервера. Для разработки сложных формул рекомендуется использовать Microsoft Visual Studio или Visual Studio Code.</p>

    <h2 id="examples">Примеры формул</h2>
    <p>Пример 1. Линейное преобразование значения канала, получаемого от устройства. Формула используется для канала типа <em>Входной</em>.</p>
    <pre><code>10 * Cnl + 1</code></pre>

    <p>Пример 2. Сумма значений каналов 101 и 102. Статус извлекается из канала 101. Формула используется для канала типа <em>Расчётный</em>.</p>
    <pre><code>Val(101) + Val(102); Stat(101)</code></pre>

    <p>Пример 3. Формула, используемая для канала расчётного типа, извлекает 0-й бит из данных канала 105.</p>
    <pre><code>GetBit(Data(105), 0)</code></pre>

    <p>Пример 4. Формула увеличивает счётчик на единицу каждую минуту, обнуляя счётчик в начале часа.</p>
    <pre><code>EveryMin(() => HourStarted() ? 0 : Val() + 1)</code></pre>
</div>
